﻿
#if DEBUG_GRAPHICS_ENABLED && true
#define TERRAIN_DEBUG_GRAPHICS
#endif

using System;
using System.Collections.Generic;
using System.Diagnostics;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SunXNA.RainbowXNA;
using NeedsMoreNinja.EventManager;
using NeedsMoreNinja;

namespace SunXNA.Terrain
{
    class CTerrain : IEventListener<SFillWorld>, IEventListener<SLoadContent>
    {
        #region Public Types
        #endregion

        #region Private Member Variables

        private List<CRainbowXNA.CRenderInstance> m_patches;
        //private List<CRainbowXNA.CRenderInstance> m_waterPatches;
        private CHeightMap m_heightmap;
        private float m_minHeight;
        private float m_maxHeight;
        private float m_cellWidth;
        private float m_cellDepth;
        //private float m_waterLevel;

        private string m_groundTextureAssetName;
        //private string m_waterTextureAssetName;

        private Vector3[] m_cellNormals;

        private Matrix m_m4World;

        #endregion

        #region Protected Member Variables
        #endregion

        #region Puplic Member Properties
        public float MinHeight
        {
            get { return m_minHeight; }
        }

        public CHeightMap HeightMap
        {
            get { return m_heightmap; }
        }

        #endregion

        #region Public Member Functions

        public CTerrain()
        {
            CEventManager<SFillWorld>.AddListener(this);
            CEventManager<SLoadContent>.AddListener(this);
        }

        public void Initialise( Texture2D heightMapImage,
                                float minHeight,
                                float maxHeight,
                                float waterLevel,
                                float cellWidth,
                                float cellDepth,
                                string groundTextureAssetName,
                                string waterTextureAssetName )
        {
            m_patches = new List<CRainbowXNA.CRenderInstance>();
            //m_waterPatches = new List<CRainbowXNA.CRenderInstance>();
            m_minHeight = minHeight;
            m_maxHeight = maxHeight;
            //m_waterLevel = waterLevel;

            m_heightmap = CHeightMap.LoadFromFile(heightMapImage, minHeight, maxHeight);

            m_cellWidth = cellWidth;
            m_cellDepth = cellDepth;
            m_groundTextureAssetName = groundTextureAssetName;
            //m_waterTextureAssetName = waterTextureAssetName;

            CalculateCellNormals();
        }

        void IEventListener<SFillWorld>.Handle(SFillWorld e)
        {
            //Trace.WriteLine("TerrainRender");

            foreach (CRainbowXNA.CRenderInstance instance in m_patches)
            {
                Singleton<CRainbowXNA>.Instance.AddPotentialVisibleObject(instance);
            }

            //foreach (CRainbowXNA.CRenderInstance instance in m_waterPatches)
            //{
            //    Singleton<CRainbowXNA>.Instance.AddPotentialVisibleObject(instance);
            //}
        }

        void IEventListener<SLoadContent>.Handle(SLoadContent e)
        {

            const int patchWidth = 20;
            const int patchDepth = 20;

            int currentX = 0;
            int currentZ = 0;

            int nextX = patchWidth;
            int nextZ = patchDepth;

            Vector3 pos;
            pos.X = ((float)m_heightmap.Width * m_cellWidth) * -0.5f;
            pos.Y = 0.0f;
            pos.Z = ((float)m_heightmap.Depth * m_cellDepth) * -0.5f;

            m_m4World = Matrix.CreateTranslation(pos);

            float xIncrement = (patchWidth) * m_cellWidth;
            float zIncrement = (patchDepth) * m_cellDepth;

            CRainbowXNA.RenderObjectHandleType waterPlaneObjectHandle = Singleton<CRainbowXNA>.Instance.CreatePlane(xIncrement, zIncrement);

            while (nextX + 1 <= m_heightmap.Width)
            {
                pos.Z = ((float)m_heightmap.Depth * m_cellDepth) * -0.5f;
                currentZ = 0;
                nextZ = patchDepth;

                while (nextZ + 1 <= m_heightmap.Depth)
                {
                    //
                    //  Terrain Patch
                    //
                    //CRainbowXNA.CRenderInstance instance = new CRainbowXNA.CRenderInstance();
                    CRainbowXNA.RenderObjectHandleType ObjectHandle = Singleton<CRainbowXNA>.Instance.LoadTerrain(m_heightmap, currentX, currentZ, nextX - currentX + 1, nextZ - currentZ + 1, m_cellWidth, m_cellDepth);
                    CRainbowXNA.CRenderInstance instance = Singleton<CRainbowXNA>.Instance.CreateRenderInstance(ObjectHandle);
                    instance.World = Matrix.CreateTranslation(pos);

                    m_patches.Add(instance);

                    //
                    //  Water Patch
                    //
                    //CRainbowXNA.CRenderInstance waterInstance = Singleton<CRainbowXNA>.Instance.CreateRenderInstance(waterPlaneObjectHandle);
                    //Vector3 waterPos = pos;
                    //waterPos.Y = m_waterLevel;
                    //waterInstance.World = Matrix.CreateTranslation(waterPos);

                    //m_waterPatches.Add(waterInstance);

                    pos.Z += zIncrement;
                    currentZ = nextZ;
                    nextZ += patchDepth;
                }

                //
                //  Add left over rows
                //
                if (nextZ + 1 > m_heightmap.Depth)
                {
                    CRainbowXNA.RenderObjectHandleType ObjectHandle = Singleton<CRainbowXNA>.Instance.LoadTerrain(m_heightmap, currentX, currentZ, nextX - currentX + 1, m_heightmap.Depth - currentZ, m_cellWidth, m_cellDepth);
                    CRainbowXNA.CRenderInstance instance = Singleton<CRainbowXNA>.Instance.CreateRenderInstance(ObjectHandle);
                    instance.World = Matrix.CreateTranslation(pos);

                    m_patches.Add(instance);

                    //
                    //  Water Patch
                    //
                    //CRainbowXNA.CRenderInstance waterInstance = Singleton<CRainbowXNA>.Instance.CreateRenderInstance(waterPlaneObjectHandle);
                    //Vector3 waterPos = pos;
                    //waterPos.Y = m_waterLevel;
                    //waterInstance.World = Matrix.CreateTranslation(waterPos);

                    //m_waterPatches.Add(waterInstance);
                }

                pos.X += xIncrement;
                currentX = nextX;
                nextX += patchWidth;
            }

            //
            //  Add left over Columns
            //
            if ( nextX + 1 > m_heightmap.Width )
            {
                pos.Z = ((float)m_heightmap.Depth * m_cellDepth) * -0.5f;
                currentZ = 0;
                nextZ = patchDepth;

                while (nextZ + 1 <= m_heightmap.Depth)
                {
                    CRainbowXNA.RenderObjectHandleType ObjectHandle = Singleton<CRainbowXNA>.Instance.LoadTerrain(m_heightmap, currentX, currentZ, m_heightmap.Width - currentX, nextZ - currentZ + 1, m_cellWidth, m_cellDepth);
                    CRainbowXNA.CRenderInstance instance = Singleton<CRainbowXNA>.Instance.CreateRenderInstance(ObjectHandle);
                    instance.World = Matrix.CreateTranslation(pos);

                    m_patches.Add(instance);

                    //
                    //  Water Patch
                    //
                    //CRainbowXNA.CRenderInstance waterInstance = Singleton<CRainbowXNA>.Instance.CreateRenderInstance(waterPlaneObjectHandle);
                    //Vector3 waterPos = pos;
                    //waterPos.Y = m_waterLevel;
                    //waterInstance.World = Matrix.CreateTranslation(waterPos);

                    //m_waterPatches.Add(waterInstance);

                    pos.Z += zIncrement;
                    currentZ = nextZ;
                    nextZ += patchDepth;
                }

                //
                //  Add left over rows
                //
                if (nextZ + 1 > m_heightmap.Depth)
                {
                    CRainbowXNA.RenderObjectHandleType ObjectHandle = Singleton<CRainbowXNA>.Instance.LoadTerrain(m_heightmap, currentX, currentZ, m_heightmap.Width - currentX, m_heightmap.Depth - currentZ, m_cellWidth, m_cellDepth);
                    CRainbowXNA.CRenderInstance instance = Singleton<CRainbowXNA>.Instance.CreateRenderInstance(ObjectHandle);
                    instance.World = Matrix.CreateTranslation(pos);

                    m_patches.Add(instance);

                    //
                    //  Water Patch
                    //
                    //CRainbowXNA.CRenderInstance waterInstance = Singleton<CRainbowXNA>.Instance.CreateRenderInstance(waterPlaneObjectHandle);
                    //Vector3 waterPos = pos;
                    //waterPos.Y = m_waterLevel;
                    //waterInstance.World = Matrix.CreateTranslation(waterPos);

                    //m_waterPatches.Add(waterInstance);
                }
            }

            Random rand = new Random();

            CRainbowXNA.TextureObjectHandleType groundTextureHandle = Singleton<CRainbowXNA>.Instance.LoadTexture(m_groundTextureAssetName);

            foreach (CRainbowXNA.CRenderInstance instance in m_patches)
            {
                //instance.Material = new SunXNA.RainbowXNA.Material.CTexturedLambertShadedMaterial();
                instance.Material = new SunXNA.RainbowXNA.Material.CTerrainMaterial();
                //instance.Material = new SunXNA.RainbowXNA.Material.CLambertShadedMaterial();

                Vector4 colour = new Vector4((float)rand.NextDouble(), (float)rand.NextDouble(), (float)rand.NextDouble(), 1.0f);

                //instance.Material.Diffuse = colour;
                instance.Material.Diffuse = Color.White.ToVector4();
                instance.Material.Ambient = new Vector4(0.1f, 0.1f, 0.1f, 1.0f);
                instance.Material.TextureHandle = groundTextureHandle;
            }

            //CRainbowXNA.TextureObjectHandleType waterTextureHandle = Singleton<CRainbowXNA>.Instance.LoadTexture(m_waterTextureAssetName);

            //foreach (CRainbowXNA.CRenderInstance instance in m_waterPatches)
            //{
            //    instance.Material = new SunXNA.RainbowXNA.Material.CTexturedLambertShadedMaterial();
            //    instance.Material.Diffuse = Color.White.ToVector4();
            //    instance.Material.Ambient = new Vector4(0.1f, 0.1f, 0.1f, 1.0f);
            //    instance.Material.TextureHandle = waterTextureHandle;
            //}
        }

        public float GetHeightAtXZ(Vector3 v3WorldPosition, out Vector3 v3OutNormal)
        {
            int cellX = (int)((v3WorldPosition.X - m_m4World.Translation.X) / m_cellWidth);
            int cellZ = (int)((v3WorldPosition.Z - m_m4World.Translation.Z) / m_cellDepth);

            if (cellX < 0 || cellZ < 0 || cellX > (m_heightmap.Width - 1) || cellZ > (m_heightmap.Depth - 1))
            {
                //Out of bounds
                v3OutNormal = Vector3.Up;
                return 0.0f;
            }

            int totalCellsAcross = (m_heightmap.Width - 1);
            int cellNormalsLeftIndex = (cellX + cellZ * totalCellsAcross) * 2;

            //
            //  GetVerts
            //
            Vector3 v3TopLeft;
            v3TopLeft.X = (((float)cellX) * m_cellWidth) + m_m4World.Translation.X;
            v3TopLeft.Y = m_heightmap.GetHeightValue(cellX, cellZ);
            v3TopLeft.Z = (((float)cellZ) * m_cellDepth) + m_m4World.Translation.Z;

            Vector3 v3TopRight = v3TopLeft + (Vector3.Right * m_cellWidth);
            v3TopRight.Y = m_heightmap.GetHeightValue(cellX + 1, cellZ);

            Vector3 v3BottomLeft = v3TopLeft + (Vector3.Backward * m_cellDepth);
            v3BottomLeft.Y = m_heightmap.GetHeightValue(cellX, cellZ + 1);

            Vector3 v3BottomRight = v3BottomLeft + (Vector3.Right * m_cellWidth);
            v3BottomRight.Y = m_heightmap.GetHeightValue(cellX + 1, cellZ + 1);

            //
            //  DeltaValus
            //
            float deltaX = v3WorldPosition.X - v3BottomLeft.X;
            float deltaZ = v3WorldPosition.Z - v3BottomLeft.Z;

            float deltaXPercentage = deltaX / m_cellWidth;  //Cells could be rectanglular, so checking percentages across is more reliable
            float deltaZPercentage = -deltaZ / m_cellDepth; //Forward is 0,0,-1, deltaZ should be nagative... fucking right handed coordinate system

            if (deltaXPercentage < deltaZPercentage)
            {
                v3OutNormal = m_cellNormals[cellNormalsLeftIndex];  //in left half

#if TERRAIN_DEBUG_GRAPHICS && false
                UInt32 key = 1234;
                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                               SunXNA.DebugGraphics.Type.Box,
                                                                               Color.Red,
                                                                               v3TopLeft,
                                                                               Vector3.Forward,
                                                                               Vector3.One * 0.5f,
                                                                               1.0f);

                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                              SunXNA.DebugGraphics.Type.Box,
                                                                              Color.Red,
                                                                              v3TopLeft + m_heightmap.GetNormal(cellX, cellZ, m_cellWidth, m_cellDepth) * 1.25f,
                                                                              m_heightmap.GetNormal(cellX, cellZ, m_cellWidth, m_cellDepth),
                                                                              new Vector3(0.1f, 0.1f, 2.5f),
                                                                              1.0f);


                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                               SunXNA.DebugGraphics.Type.Box,
                                                                               Color.Red,
                                                                               v3BottomLeft,
                                                                               Vector3.Forward,
                                                                               Vector3.One * 0.5f,
                                                                               1.0f);

                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                              SunXNA.DebugGraphics.Type.Box,
                                                                              Color.Red,
                                                                              v3BottomLeft + m_heightmap.GetNormal(cellX, cellZ + 1, m_cellWidth, m_cellDepth) * 1.25f,
                                                                              m_heightmap.GetNormal(cellX, cellZ+1, m_cellWidth, m_cellDepth),
                                                                              new Vector3(0.1f, 0.1f, 2.5f),
                                                                              1.0f);

                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                               SunXNA.DebugGraphics.Type.Box,
                                                                               Color.Red,
                                                                               v3TopRight,
                                                                               Vector3.Forward,
                                                                               Vector3.One * 0.5f,
                                                                               1.0f);

                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                              SunXNA.DebugGraphics.Type.Box,
                                                                              Color.Red,
                                                                              v3TopRight + m_heightmap.GetNormal(cellX + 1, cellZ, m_cellWidth, m_cellDepth) * 1.25f,
                                                                              m_heightmap.GetNormal(cellX+1, cellZ, m_cellWidth, m_cellDepth),
                                                                              new Vector3(0.1f, 0.1f, 2.5f),
                                                                              1.0f);

                //Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                //                                                               XNA_ImmaLearn.SunXNA.DebugGraphics.Type.Box,
                //                                                               v3BottomRight,
                //                                                               Vector3.Forward,
                //                                                               Vector3.One * 0.5f,
                //                                                               1.0f);
#endif
            }
            else
            {
                v3OutNormal = m_cellNormals[cellNormalsLeftIndex + 1]; // in right normals

#if TERRAIN_DEBUG_GRAPHICS && false
                UInt32 key = 1234;
                //Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                //                                                               XNA_ImmaLearn.SunXNA.DebugGraphics.Type.Box,
                //                                                               v3TopLeft,
                //                                                               Vector3.Forward,
                //                                                               Vector3.One * 0.5f,
                //                                                               1.0f);

                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                               XNA_ImmaLearn.SunXNA.DebugGraphics.Type.Box,
                                                                               v3BottomLeft,
                                                                               Vector3.Forward,
                                                                               Vector3.One * 0.5f,
                                                                               1.0f);

                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                              XNA_ImmaLearn.SunXNA.DebugGraphics.Type.Box,
                                                                              v3BottomLeft + m_heightmap.GetNormal(cellX, cellZ + 1, m_cellWidth, m_cellDepth) * 1.25f,
                                                                              m_heightmap.GetNormal(cellX, cellZ + 1, m_cellWidth, m_cellDepth),
                                                                              new Vector3(0.1f, 0.1f, 2.5f),
                                                                              1.0f);

                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                               XNA_ImmaLearn.SunXNA.DebugGraphics.Type.Box,
                                                                               v3TopRight,
                                                                               Vector3.Forward,
                                                                               Vector3.One * 0.5f,
                                                                               1.0f);

                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                              XNA_ImmaLearn.SunXNA.DebugGraphics.Type.Box,
                                                                              v3TopRight + m_heightmap.GetNormal(cellX + 1, cellZ, m_cellWidth, m_cellDepth) * 1.25f,
                                                                              m_heightmap.GetNormal(cellX+1, cellZ, m_cellWidth, m_cellDepth),
                                                                              new Vector3(0.1f, 0.1f, 2.5f),
                                                                              1.0f);

                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                               XNA_ImmaLearn.SunXNA.DebugGraphics.Type.Box,
                                                                               v3BottomRight,
                                                                               Vector3.Forward,
                                                                               Vector3.One * 0.5f,
                                                                               1.0f);

                Singleton<SunXNA.DebugGraphics.CManager>.Instance.Add3DObject(++key,
                                                                              XNA_ImmaLearn.SunXNA.DebugGraphics.Type.Box,
                                                                              v3BottomRight + m_heightmap.GetNormal(cellX + 1, cellZ + 1, m_cellWidth, m_cellDepth) * 1.25f,
                                                                              m_heightmap.GetNormal(cellX + 1, cellZ + 1, m_cellWidth, m_cellDepth),
                                                                              new Vector3(0.1f, 0.1f, 2.5f),
                                                                              1.0f);
#endif
            }

            float height = v3BottomLeft.Y + ((deltaX * v3OutNormal.X + deltaZ * v3OutNormal.Z) / -v3OutNormal.Y);

            Trace.Assert((height >= v3TopLeft.Y) || (height >= v3TopRight.Y) || (height >= v3BottomLeft.Y) || (height >= v3BottomRight.Y));

            return height;
        }

        #endregion

        #region Protected Member Functions
        #endregion

        #region Private Member Functions

        private void CalculateCellNormals()
        {
            int cellsWide = m_heightmap.Width - 1;
            int cellsDeep = m_heightmap.Depth - 1;

            int totalCells = cellsWide * cellsDeep;
            const int trianglesPerCell = 2;

            int totalTriangles = totalCells * trianglesPerCell;

            //m_m4World

            m_cellNormals = new Vector3[totalTriangles];

            int triangleIndex = 0;
            for (int z = 0; z < cellsDeep; ++z)
            {
                for (int x = 0; x < cellsWide; ++x)
                {
                    Vector3 v3TopLeft;
                    v3TopLeft.X = x * m_cellWidth;
                    v3TopLeft.Y = m_heightmap.GetHeightValue(x, z);
                    v3TopLeft.Z = z * m_cellDepth;

                    Vector3 v3TopRight = v3TopLeft + (Vector3.Right * m_cellWidth);
                    Vector3 v3BottomLeft = v3TopLeft + (Vector3.Backward * m_cellDepth);
                    Vector3 v3BottomRight = v3TopLeft + (Vector3.Right * m_cellWidth) + (Vector3.Backward * m_cellDepth);

                    v3TopRight.Y    =   m_heightmap.GetHeightValue(x + 1, z);
                    v3BottomLeft.Y  =   m_heightmap.GetHeightValue(x, z + 1);
                    v3BottomRight.Y =   m_heightmap.GetHeightValue(x + 1, z + 1);

                    //
                    //  Left Triangle
                    //
                    Vector3 v3TRtoTL = v3TopLeft - v3TopRight;
                    Vector3 v3RLtoTL = v3TopLeft - v3BottomLeft;

                    Vector3 v3LeftNormal = Vector3.Cross(v3RLtoTL, v3TRtoTL);
                    v3LeftNormal.Normalize();

                    //m_cellNormals[triangleIndex++] = v3LeftNormal;

                    m_cellNormals[triangleIndex++] = v3LeftNormal;

                    Trace.Assert(Vector3.Dot(v3LeftNormal, Vector3.Up) > 0.0f);

                    //
                    //  Right Triangle
                    //
                    Vector3 v3TRtoBL = v3BottomLeft - v3TopRight;
                    Vector3 v3BRtoBL = v3BottomLeft - v3BottomRight;

                    Vector3 v3RightNormal = Vector3.Cross(v3BRtoBL, v3TRtoBL);
                    //Vector3 v3RightNormal = Vector3.Cross(v3RightToBottom, v3RightBottomToBottom);
                    v3RightNormal.Normalize();

                    //m_cellNormals[triangleIndex++] = v3RightNormal;
                    m_cellNormals[triangleIndex++] = v3RightNormal;

                    Trace.Assert(Vector3.Dot(v3RightNormal, Vector3.Up) > 0.0f);
                }
            }

            Trace.Assert(triangleIndex == totalTriangles);
        }

        #endregion
    }
}
